package boardgamer.yavalath.common;

import boardgamer.common.CyclicIterator;
import boardgamer.common.GameState;
import boardgamer.common.annotations.Immutable;
import boardgamer.common.net.PlayerId;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;

@Immutable
public class YavalathGameState implements GameState, Serializable {

  private static final long serialVersionUID = 6665680945159830058L;

  public enum Phase {
    PLAYER_SETUP, // Game has not started, players are picking their colors
    GAME,         // Game has started and is still going on
    END_GAME      // Game has ended
  }

  private final Phase phase;
  private final ImmutableMap<PlayerId, YavalathPlayerState> playerStates;

  /**
   * The next playerId returned by the iterator is the current player.
   */
  private final CyclicIterator<PlayerId> currentPlayerIterator;
  private final YavalathBoard board;
  private final ImmutableMap<PlayerId, ImmutableSet<YavalathTile>> nextWinningTiles;

  private YavalathGameState(Builder builder) {
    this.phase = checkNotNull(builder.phase, "phase");
    this.playerStates = ImmutableMap.copyOf(checkNotNull(builder.playerStates, "playerStates"));
    this.currentPlayerIterator = CyclicIterator.copyOf(builder.activePlayers);
    this.board = checkNotNull(YavalathBoard.copyOf(builder.board), "board");

    Map<PlayerId, ImmutableSet<YavalathTile>> tiles = Maps.newHashMap();
    for (PlayerId playerId : builder.nextWinningTiles.keySet()) {
      tiles.put(playerId, ImmutableSet.copyOf(builder.nextWinningTiles.get(playerId)));
    }
    this.nextWinningTiles = ImmutableMap.copyOf(tiles);
  }

  public ImmutableMap<PlayerId, YavalathPlayerState> getPlayerStates() {
    return playerStates;
  }

  public Phase getPhase() {
    return phase;
  }

  public YavalathBoard getBoard() {
    return board;
  }

  public PlayerId getCurrentPlayer() {
    return currentPlayerIterator.peek();
  }

  public CyclicIterator<PlayerId> getActivePlayers() {
    return currentPlayerIterator;
  }

  public boolean isSwappingAllowed() {
    return !currentPlayerIterator.hasCycled();
  }

  public ImmutableSet<YavalathTile> getNextWinningMoves(PlayerId playerId) {
    if (nextWinningTiles.containsKey(playerId)) {
      return nextWinningTiles.get(playerId);
    } else {
      return ImmutableSet.of();
    }
  }
  
  public Set<PlayerId> getLosers() {
    return Sets.difference(playerStates.keySet(), ImmutableSet.copyOf(getActivePlayers().asList()));
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof YavalathGameState)) {
      return false;
    }

    YavalathGameState that = (YavalathGameState) obj;
    return Objects.equal(this.board, that.board)
        && Objects.equal(this.currentPlayerIterator, that.currentPlayerIterator)
        && Objects.equal(this.phase, that.phase)
        && Objects.equal(this.playerStates, that.playerStates)
        && Objects.equal(this.nextWinningTiles, that.nextWinningTiles);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(board, currentPlayerIterator, phase, playerStates);
  }

  @Override
  public String toString() {
    return Objects.toStringHelper(this)
        .add("board", board)
        .add("currentPlayerIterator", currentPlayerIterator)
        .add("phase", phase)
        .add("playerStates", playerStates)
        .add("nextWinningTiles", nextWinningTiles)
        .toString();
  }

  public static class Builder {
    private Phase phase;
    private ImmutableMap<PlayerId, YavalathPlayerState> playerStates;
    private HashMap<PlayerId, HashSet<YavalathTile>> nextWinningTiles;
    private YavalathBoard board;
    private CyclicIterator<PlayerId> activePlayers;

    public static Builder fromPrototype(YavalathGameState prototype) {
      return new Builder()
          .phase(prototype.phase)
          .playerStates(prototype.playerStates)
          .board(prototype.board)
          .activePlayers(prototype.getActivePlayers())
          .nextWinningTiles(prototype.nextWinningTiles);
    }

    public Builder() {
      this.nextWinningTiles = Maps.newHashMap();
    }

    public Builder phase(Phase phase) {
      this.phase = phase;
      return this;
    }

    public Builder playerStates(ImmutableMap<PlayerId, YavalathPlayerState> playerStates) {
      this.playerStates = playerStates;
      return this;
    }

    public Builder board(YavalathBoard board) {
      this.board = board;
      return this;
    }

    public Builder activePlayers(CyclicIterator<PlayerId> activePlayers) {
      this.activePlayers = activePlayers;
      return this;
    }

    public Builder nextWinningTiles(Map<PlayerId, ? extends Set<YavalathTile>> nextWinningTiles) {
      Map<PlayerId, HashSet<YavalathTile>> tiles = Maps.newHashMap();
      for (PlayerId playerId : nextWinningTiles.keySet()) {
        tiles.put(playerId, Sets.newHashSet(nextWinningTiles.get(playerId)));
      }
      this.nextWinningTiles = Maps.newHashMap(tiles);
      return this;
    }

    public Builder setPlayerWinningtiles(PlayerId playerId, Set<YavalathTile> nextWinningTiles) {
      this.nextWinningTiles.put(playerId, Sets.newHashSet(nextWinningTiles));
      return this;
    }

    public YavalathGameState build() {
      return new YavalathGameState(this);
    }
  }
}
